import type {
    IWorkflowStatesWidgetRepository,
    IWorkflowStatesWidgetRepositoryListResult
} from "~/Repositories/abstractions/WorkflowStatesWidgetRepository.js";
import type {
    IWorkflowStatesWidgetPresenter,
    IWorkflowStatesWidgetPresenterViewModel,
    IWorkflowStatesWidgetPresenterViewModelValues
} from "./abstractions/WorkflowStatesWidgetPresenter.js";
import { type IWorkflowState, WorkflowStateValue } from "~/types.js";
import { makeAutoObservable, observable, runInAction, set, toJS } from "mobx";
import type { NonEmptyArray } from "@webiny/app/types.js";

export interface IWorkflowStatesWidgetPresenterParams {
    repository: IWorkflowStatesWidgetRepository;
    type: "own" | "requested";
    states: NonEmptyArray<WorkflowStateValue>;
}

interface IResultData {
    key: WorkflowStateValue;
    items: IWorkflowState[];
    totalCount: number;
}

const mapListStatesResponse = (key: WorkflowStateValue) => {
    return (input: IWorkflowStatesWidgetRepositoryListResult): IResultData => {
        return {
            key,
            items: input.items,
            totalCount: input.totalCount
        };
    };
};

const mapPromiseAllResponse = (
    results: IResultData[]
): IWorkflowStatesWidgetPresenterViewModelValues => {
    return results.reduce<IWorkflowStatesWidgetPresenterViewModelValues>((output, result) => {
        output[result.key] = {
            items: result.items,
            total: result.totalCount
        };
        return output;
    }, {});
};

interface IMoveStepBetweenStatesParams {
    from: IWorkflowState;
    to: IWorkflowState;
}

export class WorkflowStatesWidgetPresenter implements IWorkflowStatesWidgetPresenter {
    readonly #type;
    readonly #states;
    private readonly repository;
    private dialog:
        | "approve"
        | "reject"
        | "approve:success"
        | "reject:success"
        | "start"
        | "start:success"
        | "takeOver"
        | "takeOver:success"
        | null;
    private state: IWorkflowState | null;
    private readonly values;

    public get vm(): IWorkflowStatesWidgetPresenterViewModel {
        return {
            type: this.#type,
            states: this.#states,
            loading: toJS(this.repository.loading),
            error: toJS(this.repository.error),
            values: toJS(this.values),
            dialogLoading: this.repository.actionLoading,
            dialogError: this.repository.actionError,
            showStartDialog: this.dialog === "start" && this.state ? this.state : null,
            showStartSuccessDialog:
                this.dialog === "start:success" && this.state ? this.state : null,
            showApproveDialog: this.dialog === "approve" && this.state ? this.state : null,
            showApproveSuccessDialog:
                this.dialog === "approve:success" && this.state ? this.state : null,
            showRejectDialog: this.dialog === "reject" && this.state ? this.state : null,
            showRejectSuccessDialog:
                this.dialog === "reject:success" && this.state ? this.state : null,
            showTakeOverDialog: this.dialog === "takeOver" && this.state ? this.state : null,
            showTakeOverSuccessDialog:
                this.dialog === "takeOver:success" && this.state ? this.state : null
        };
    }

    public constructor(params: IWorkflowStatesWidgetPresenterParams) {
        this.repository = params.repository;
        this.#type = params.type;
        this.#states = params.states;
        this.values = observable.object<IWorkflowStatesWidgetPresenterViewModelValues>({});
        this.dialog = null;
        this.state = null;

        makeAutoObservable(this);

        this.init();
    }

    private async init(): Promise<void> {
        const result = await this.load();

        runInAction(() => {
            for (const key in result) {
                set(this.values, key, result[key]);
            }
        });
    }

    private async load(): Promise<IWorkflowStatesWidgetPresenterViewModelValues> {
        if (this.#type === "requested") {
            return await this.initRequested();
        }
        return await this.initOwn();
    }

    private async initOwn() {
        return await Promise.all(
            this.#states.map(state => {
                return this.repository
                    .listOwnStates({
                        state,
                        limit: 5
                    })
                    .then(mapListStatesResponse(state));
            })
        ).then(mapPromiseAllResponse);
    }

    private async initRequested() {
        return await Promise.all(
            this.#states.map(state => {
                return this.repository
                    .listRequestedStates({
                        state,
                        limit: 5
                    })
                    .then(mapListStatesResponse(state));
            })
        ).then(mapPromiseAllResponse);
    }

    private increaseTotals(key: WorkflowStateValue): void {
        const value = this.values[key];
        if (!value) {
            set(this.values, key, {
                items: [],
                total: 1
            });
            return;
        }
        set(this.values, key, {
            ...value,
            total: value.total + 1
        });
    }

    private decreaseTotals(key: WorkflowStateValue): void {
        const value = this.values[key];
        if (!value?.total) {
            return;
        }

        set(this.values, key, {
            ...value,
            total: value.total - 1
        });
    }

    private moveStepBetweenStates(params: IMoveStepBetweenStatesParams): void {
        const { from: fromState, to: toState } = params;
        if (fromState.state === toState.state) {
            return;
        }
        const from = fromState.state;
        const to = toState.state;

        const fromIndex = this.values[from].items.findIndex(item => item.id === fromState.id);
        if (fromIndex === -1) {
            return;
        }
        const fromItems = Array.from(this.values[from].items);
        fromItems.splice(fromIndex, 1);
        set(this.values, from, {
            ...this.values[from],
            items: fromItems
        });

        const toItems = Array.from(this.values[to]?.items || []);
        toItems.unshift(toState);
        set(this.values, to, {
            total: this.values[to]?.total || 0,
            items: toItems
        });
    }

    startStateStep = async (state: IWorkflowState): Promise<void> => {
        const key = WorkflowStateValue.pending;
        const values = this.values[key];
        const items = values.items || [];
        const index = items.findIndex(item => item.id === state.id);
        if (index === -1) {
            return;
        }
        const original = items[index];
        const result = await this.repository.startStateStep({
            id: state.id
        });
        if (!result) {
            return;
        }
        runInAction(() => {
            this.moveStepBetweenStates({
                from: original,
                to: result
            });
            if (result.state === WorkflowStateValue.inReview) {
                this.decreaseTotals(WorkflowStateValue.pending);
                this.increaseTotals(WorkflowStateValue.inReview);
            }
            this.state = result;
            this.dialog = "start:success";
        });
    };

    takeOverStateStep = async (state: IWorkflowState): Promise<void> => {
        const key = WorkflowStateValue.inReview;
        const values = this.values[key];
        const items = values.items || [];
        const index = items.findIndex(item => item.id === state.id);
        if (index === -1) {
            return;
        }
        const result = await this.repository.takeOverStateStep({
            id: state.id
        });
        if (!result) {
            return;
        }
        runInAction(() => {
            items[index] = result;
            set(this.values, key, {
                ...values,
                items
            });
            this.state = result;
            this.dialog = "takeOver:success";
        });
    };

    approveStateStep = async (state: IWorkflowState, comment?: string): Promise<void> => {
        const key = WorkflowStateValue.inReview;
        const values = this.values[key];
        const items = values.items || [];
        const original = items.find(item => item.id === state.id);
        if (!original) {
            return;
        }

        const result = await this.repository.approveStateStep({
            id: state.id,
            comment
        });
        if (!result) {
            return;
        }
        runInAction(() => {
            this.moveStepBetweenStates({
                from: original,
                to: result
            });
            if (result.state === WorkflowStateValue.pending) {
                this.increaseTotals(WorkflowStateValue.pending);
            }
            this.decreaseTotals(WorkflowStateValue.inReview);
            this.state = result;
            this.dialog = "approve:success";
        });
    };

    rejectStateStep = async (state: IWorkflowState, comment: string): Promise<void> => {
        const key = WorkflowStateValue.inReview;
        const values = this.values[key];
        const items = values.items || [];
        const original = items.find(item => item.id === state.id && item.state);
        if (!original) {
            return;
        }
        const result = await this.repository.rejectStateStep({
            id: state.id,
            comment
        });
        if (!result) {
            return;
        }

        runInAction(() => {
            this.moveStepBetweenStates({
                from: original,
                to: result
            });
            if (result.state === WorkflowStateValue.rejected) {
                this.decreaseTotals(WorkflowStateValue.inReview);
            }
            this.state = result;
            this.dialog = "reject:success";
        });
    };

    showStartStateStepDialog = (state: IWorkflowState): void => {
        runInAction(() => {
            this.state = state;
            this.dialog = "start";
        });
    };

    showTakeOverStateStepDialog = (state: IWorkflowState): void => {
        runInAction(() => {
            this.state = state;
            this.dialog = "takeOver";
        });
    };

    showApproveStateStepDialog = (state: IWorkflowState): void => {
        runInAction(() => {
            this.state = state;
            this.dialog = "approve";
        });
    };

    showRejectStateStepDialog = (state: IWorkflowState): void => {
        runInAction(() => {
            this.state = state;
            this.dialog = "reject";
        });
    };

    hideDialog = (): void => {
        runInAction(() => {
            this.state = null;
            this.dialog = null;
        });
    };
}
